#!/bin/sh /etc/rc.common

START=90
SERVICE_USE_PID=1
SERVICE_WRITE_PID=1
SERVICE_DAEMONIZE=1
EXTRA_COMMANDS="firewall hotplug switch"

APP='shadowsocks'
BIN_REDIR='/usr/bin/ss-redir'
BIN_LOCAL='/usr/bin/ss-local'
IP_IGNORE_CHINA='/etc/ipset/china'
IP_IGNORE_USER='/etc/ipset/ignore'
IP_SPECIFY_GFW='/etc/ipset/gfw'
IP_SPECIFY_USER='/etc/ipset/specify'
IPSET_GFW='/etc/ipset/gfwlist'
IPSET_USER='/etc/ipset/domain'
DNSMASQ_CONF='/etc/dnsmasq.conf'
TCP_FASTOPEN_CONF='/proc/sys/net/ipv4/tcp_fastopen'
SHADOWSOCKS_SERVER_LIST='/etc/shadowsocks.list'
NS_SERVER='114.114.114.114'
IP_RETURN='0.0.0.0/8,10.0.0.0/8,100.64.0.0/10,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.0.0.0/24,192.0.2.0/24,192.88.99.0/24,192.168.0.0/16,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,224.0.0.0/4,240.0.0.0/4,255.255.255.255'

check_prerouting() {
	if iptables -t nat --list zone_lan_prerouting &>/dev/null; then
		PREROUTING='zone_lan_prerouting'
	else
		PREROUTING='PREROUTING'
	fi
}

iptables_flush() {
	iptables-restore <<-EOF || {
		$(iptables-save -t nat|grep -v 'SHADOWSOCKS')
	EOF
		[ -z "$PREROUTING" ] && check_prerouting
		iptables -t nat -F SHADOWSOCKS_LAN
		iptables -t nat -F SHADOWSOCKS_WAN
		iptables -t nat -D $PREROUTING -p tcp -j SHADOWSOCKS_LAN
		iptables -t nat -D OUTPUT -p tcp -j SHADOWSOCKS_WAN
		iptables -t nat -X SHADOWSOCKS_LAN
		iptables -t nat -X SHADOWSOCKS_WAN
	}
	return $?
}

iptables_restore() {
	iptables-restore -n <<-EOF
		$(echo -e "$1")
	EOF
	return $?
}

ipset_flush() {
	ipset destroy "$APP" &>/dev/null
}

ipset_restore() {
	ipset version &>/dev/null || return 1
	ipset_flush
	ipset -! -R <<-EOF
		create $APP hash:net
		$(echo -e "$1" | sed -e "s/^/add $APP /")
	EOF
	return $?
}

mode_dnsmasq_enable() {
	[ -z "$1" ] && return 1
	IPSET_DOMAIN=$( echo -e "$1" | grep -Eo '[.]?([-0-9a-zA-Z]+\.)+[a-zA-Z]{2,4}' | sed -e "s/\(.*\)/ipset=\/\1\/shadowsocks/" )
	[ -z "$IPSET_DOMAIN" ] || {
			echo -e "#${APP}_start\n$IPSET_DOMAIN\n#${APP}_end">>"$DNSMASQ_CONF"
			pidof dnsmasq &>/dev/null && /etc/init.d/dnsmasq restart
			return 0
	}
	return 1
}

mode_dnsmasq_disable() {
	FILE_DATA=$(cat "$DNSMASQ_CONF")
	FIND_START=$(echo -e "$FILE_DATA"|grep -n -x -m 1 "#${APP}_start")
	[ -z "$FIND_START" ] || {
		FIND_END=$(echo -e "$FILE_DATA"|grep -n -x -m 1 "#${APP}_end")
	}
	[ -z "$FIND_END" ] || {
		sed -i "${FIND_START%%:*},${FIND_END%%:*}d" "$DNSMASQ_CONF"
		pidof dnsmasq &>/dev/null && /etc/init.d/dnsmasq restart
	}
}

tcp_fast_open() {
	if [ "$tcp_fast_open_enabled" = '1' ]; then
		[ $( cat $TCP_FASTOPEN_CONF ) = '3' ] || echo 3 > $TCP_FASTOPEN_CONF
	else
		[ $( cat $TCP_FASTOPEN_CONF ) = '3' ] || echo 0 > $TCP_FASTOPEN_CONF
	fi
}

resolve_ip() {
	[ -z "$1" ] && return 1
	if ( command -v resolveip &>/dev/null ); then
		IP_SERVERS=$( resolveip -t 10 "$1" )
	else
		IP_SERVERS=$( nslookup "$1" "$NS_SERVER" | awk 'NR>=5{print $3}' )
	fi
	[ -z "$IP_SERVERS" ] || echo -e "$IP_SERVERS"
}

host_check() {
	[ -z "$server_host" ] && return 1
	echo "Shadowsocks Server: $server_host"
	if ( echo "$server_host" | grep -q '\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}' ); then
		IP_SERVERS="$server_host"
		IP_SERVER="$server_host"
	else
		resolve_ip "$server_host"
		if [ -z "$IP_SERVERS" ]; then
			echo "Resolve host ip ... Failed"
			return 1
		else
			IP_SERVER=$( echo -e "$IP_SERVERS" | head -n 1 )
			echo "Resolve host ip ... $IP_SERVER"
		fi
	fi
	return 0	
}

redir_rules() {
	[ -z "$redir_port" ] && return 1
	[ -z "$PREROUTING" ] && check_prerouting

	IP_RETURN="$(echo $IP_RETURN | tr ',' '\n')\n"$IP_SERVERS
	[ -f "$SHADOWSOCKS_SERVER_LIST" ] && {
		IP_RETURN_SERVER=$(cat $SHADOWSOCKS_SERVER_LIST | grep -o '\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}')
		[ -z "$IP_RETURN_SERVER" ] || IP_RETURN=$IP_RETURN"\n$IP_RETURN_SERVER"
	}

	IPTABLES_RULES="*nat\n:SHADOWSOCKS_WAN - [0:0]\n:SHADOWSOCKS_LAN - [0:0]\n"

	config_get lan_control_mode config lan_control_mode '0'
	[ "$lan_control_mode" = '0' ] || IP_LAN=$(config_list_foreach config lan_ip echo)
	case $lan_control_mode in
		0)
			echo "LAN Control ... Disabled"
			IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_LAN -p tcp -j SHADOWSOCKS_WAN\n"
			;;
		1)
			echo "LAN Control ... Allow listed only"
			[ -z "$IP_LAN" ] || IPTABLES_RULES_LAN=$(echo -e "$IP_LAN" | sed "s/\(.*\)/-A SHADOWSOCKS_LAN -s \1 -p tcp -j SHADOWSOCKS_WAN/")
			[ -z "$IPTABLES_RULES_LAN" ] || IPTABLES_RULES=$IPTABLES_RULES$IPTABLES_RULES_LAN"\n"
			IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_LAN -p tcp -j RETURN\n"
			;;
		2)
			echo "LAN Control ... Allow all except listed"
			[ -z "$IP_LAN" ] || IPTABLES_RULES_LAN=$(echo -e "$IP_LAN" | sed "s/\(.*\)/-A SHADOWSOCKS_LAN -s \1 -j RETURN/")
			[ -z "$IPTABLES_RULES_LAN" ] || IPTABLES_RULES=$IPTABLES_RULES$IPTABLES_RULES_LAN"\n"
			IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_LAN -p tcp -j SHADOWSOCKS_WAN\n"
			;;
	esac

	config_get redir_model config redir_model '0'
	case $redir_model in
		0)
			echo "Proxy Mode ... Global"
			[ -z "$IP_RETURN" ] || IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN" | sed "s/\(.*\)/-A SHADOWSOCKS_WAN -d \1 -j RETURN/")"\n"
			IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_WAN -p tcp -j REDIRECT --to-ports $redir_port\n"
			;;
		1)	
			echo "Proxy Mode ... Ignore ips"
			[ -z "$IP_RETURN" ] || IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN" | sed "s/\(.*\)/-A SHADOWSOCKS_WAN -d \1 -j RETURN/")"\n"
			config_get ignore_china config ignore_china
			[ -f "$IP_IGNORE_USER" ] && IP_RETURN_LIST=$(cat "$IP_IGNORE_USER")
			[ "$ignore_china" = '1' ] && [ -f "$IP_IGNORE_CHINA" ] && IP_RETURN_LIST=$IP_RETURN_LIST"\n"$(cat "$IP_IGNORE_CHINA")
			IP_RETURN_LIST=$(echo -e "$IP_RETURN_LIST" | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?')
			[ -z "$IP_RETURN_LIST" ] || {
				if ipset_restore "$IP_RETURN_LIST"; then
					IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_WAN -p tcp -m set --match-set $APP dst -j RETURN\n"
				else
					IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN_LIST" | sed "s/\(.*\)/-A SHADOWSOCKS_WAN -d \1 -j RETURN/")"\n"
				fi
				IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_WAN -p tcp -j REDIRECT --to-ports $redir_port\n"
			}
			;;
		2)
			echo "Proxy Mode ... Specify ips"
			config_get specify_gfw config specify_gfw
			[ -f "$IP_SPECIFY_USER" ] && IP_REDIR_LIST=$(cat "$IP_SPECIFY_USER")
			[ "$specify_gfw" = '1' ] && [ -f "$IP_SPECIFY_GFW" ] && IP_REDIR_LIST=$IP_REDIR_LIST"\n"$(cat "$IP_SPECIFY_GFW")
			IP_REDIR_LIST=$(echo -e "$IP_REDIR_LIST" | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?')
			[ -z "$IP_REDIR_LIST" ] || {
				if ipset_restore "$IP_REDIR_LIST"; then
					IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_WAN -p tcp -m set --match-set $APP dst -j REDIRECT --to-ports $redir_port\n"
				else
					IPTABLES_RULES=$IPTABLES_RULES$(echo -e "$IP_RETURN_LIST" | sed "s/\(.*\)/-A SHADOWSOCKS_WAN -d \1 -p tcp -j REDIRECT --to-ports ${redir_port}/")"\n"
				fi
			}
			;;
		3)
			echo "Proxy Mode ... Specify domains"
			config_get ipset_gfw config ipset_gfw '0'
			dnsmasq --help | grep -q 'ipset' && {
				ipset_restore && {
					[ -f "$IPSET_USER" ] && IPSET_LIST=$(cat "$IPSET_USER")
					[ "$ipset_gfw" = '1' ] && [ -f "$IPSET_GFW" ] && IPSET_LIST=$IPSET_LIST"\n"$(cat "$IPSET_GFW")
					[ -z "$IPSET_LIST" ] || {
						mode_dnsmasq_enable "$IPSET_LIST" && IPTABLES_RULES=$IPTABLES_RULES"-A SHADOWSOCKS_WAN -p tcp -m set --match-set shadowsocks dst -j REDIRECT --to-ports $redir_port\n"
					}
				}
			}
	esac

	IPTABLES_RULES=$IPTABLES_RULES"-A $PREROUTING -p tcp -j SHADOWSOCKS_LAN\n-A OUTPUT -p tcp -j SHADOWSOCKS_WAN\nCOMMIT"

	if iptables_restore "$IPTABLES_RULES"; then
		tcp_fast_open
		echo "Setting ... OK"
	else
		echo "Setting ... Failed"
		redir_stop
	fi
}

service_options() {
	config_load "$APP"
	config_get server_host config server_host '127.0.0.1'
	config_get server_port config server_port '1080'
	config_get password config password 'password'
	config_get encrypt_method config encrypt_method 'table'
	config_get timeout config timeout '60'
	config_get tcp_fast_open_enabled config tcp_fast_open_enabled '0'
	LOCAL_ADDRESS='0.0.0.0'
	[ "$tcp_fast_open_enabled" = '1' ] && OPTION_FASTOPEN=' --fast-open'
}

service_check() {
	FIND_DATA=$(ps|grep "$1"|grep -v "grep $1")
	[ -z "$FIND_DATA" ] && return 1
	return 0
}

service_process() {
	service_check "$1" && echo "Start $1 ... Already running. Please try \"stop\" or \"restart\"" && return 1
	service_start $1 $2
	service_check "$1" && echo "Start $1 ... OK" && return 0
	echo "Start $1 ... Fail" && return 1
}

redir_start() {
	service_options
	host_check || return 1
	config_get redir_port config redir_port '10086'
	OPTIONS="-s $IP_SERVER -p $server_port -m $encrypt_method -k $password -b $LOCAL_ADDRESS -l $redir_port -t $timeout$OPTION_FASTOPEN"
	service_process "$BIN_REDIR" "$OPTIONS" || return 1
	redir_rules
}

local_start() {
	service_options
	host_check || return 1
	config_get socks5_port config socks5_port '10000'
	OPTIONS="-s $IP_SERVER -p $server_port -k $password -m $encrypt_method -b $LOCAL_ADDRESS -l $socks5_port -t $timeout -u$OPTION_FASTOPEN"
	service_process "$BIN_LOCAL" "$OPTIONS" || return 1
	tcp_fast_open
}

start() {
	config_load "$APP"
	config_get enabled config enabled '0'
	case $enabled in
		1)
			redir_start
			;;
		2)
			local_start
			;;
	esac
}

local_stop() {
	killall -q -9 ${BIN_LOCAL##*/}
}

redir_stop() {
	iptables_flush
	ipset_flush
	killall -q -9 ${BIN_REDIR##*/}
	mode_dnsmasq_disable
}

stop() {
	redir_stop
	local_stop
	tcp_fast_open
}

firewall() {
	redir_stop
	redir_start
}

hotplug() {
	restart
}

switch() {
	[ -z "$1" -o -z "$2" ] && return 1
	service_stop $1 && service_start $1 $2
}
